package com.nageoffer.jobtrain.projects.service.impl;

import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.github.javafaker.Faker;
import com.nageoffer.jobtrain.common.toolkit.constant.JWTPlayLoadConstant;
import com.nageoffer.jobtrain.common.toolkit.utils.JWTUtils;
import com.nageoffer.jobtrain.common.web.exception.ClientException;
import com.nageoffer.jobtrain.projects.common.constant.RedisCacheConstant;
import com.nageoffer.jobtrain.projects.common.enums.UserSexEnums;
import com.nageoffer.jobtrain.projects.common.enums.UserStatusEnums;
import com.nageoffer.jobtrain.projects.config.AliYunSendMessageConfig;
import com.nageoffer.jobtrain.projects.dao.entity.UserDO;
import com.nageoffer.jobtrain.projects.dao.mapper.UserMapper;
import com.nageoffer.jobtrain.projects.dto.req.UserLoginReqDTO;
import com.nageoffer.jobtrain.projects.dto.req.UserSendSmsMessageReqDTO;
import com.nageoffer.jobtrain.projects.dto.resp.UserLoginRespDTO;
import com.nageoffer.jobtrain.projects.service.LoginService;
import com.nageoffer.jobtrain.projects.toolkit.PhoneNumberValidatorUtils;
import lombok.RequiredArgsConstructor;
import org.redisson.api.RBloomFilter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * author：唐晨
 */
@Service
@RequiredArgsConstructor
public class LoginServiceImpl implements LoginService {
    private final UserMapper userMapper;
    private final StringRedisTemplate stringRedisTemplate;
    private final AliYunSendMessageConfig aliYunSendMessageConfig;
    private final RBloomFilter<String> userRegisterCachePenetrationBloomFilter;

    /**
     * 默认头像地址
     */
    @Value("${minio.defaultHeadShot}")
    private String defaultHeadShot;

    /**
     * 手机验证码总有效期时间 -3分钟
     */
    public static final long smsExpire = 60 * 3;

    /**
     * 用户登录、注册token总的有效期时间 -3天
     */
    public static final long tokenExpire = 60 * 60 * 24 * 3;

    @Override
    public void sendSmsMessageCode(UserSendSmsMessageReqDTO requestParam) {
        String phoneNumber = requestParam.getPhoneNumber();
        // 验证手机号是否有效
        if (!PhoneNumberValidatorUtils.isValidPhoneNumber(phoneNumber)) {
            throw new ClientException("手机号格式不正确，请输入有效的手机号！");
        }
        // 使用hutool生成验证码字符串
        String smsCode = RandomUtil.randomNumbers(6);
        boolean sendResult = aliYunSendMessageConfig.sendMessage(phoneNumber, smsCode);
        if (BooleanUtil.isFalse(sendResult)) {
            // 此时说明验证码发送失败，可能是手机号错误等情况
            throw new ClientException("手机验证码发送异常！");
        }
        stringRedisTemplate.opsForValue().set(String.format(RedisCacheConstant.USER_SEND_MESSAGE_KEY, Long.valueOf(phoneNumber)),
                smsCode, smsExpire, TimeUnit.SECONDS);
    }

    @Override
    public UserLoginRespDTO loginUser(UserLoginReqDTO requestParam) {
        // 先进行判断验证码的正确与否
        String smsMessageCode = stringRedisTemplate.opsForValue().get(String.format(RedisCacheConstant.USER_SEND_MESSAGE_KEY, Long.valueOf(requestParam.getPhoneNumber())));
        if (StrUtil.isEmpty(smsMessageCode)) {
            throw new ClientException("该验证码已过期，请重新申请！");
        }
        if (!Objects.equals(requestParam.getSmsMessageCode(), smsMessageCode)) {
            throw new ClientException("验证码输入错误！");
        }
        // 登录验证码成功、删除验证码缓存
        stringRedisTemplate.delete(String.format(RedisCacheConstant.USER_SEND_MESSAGE_KEY, Long.valueOf(requestParam.getPhoneNumber())));
        // 此时先使用布隆过滤器判断用户是否可能存在
        boolean isExist = userRegisterCachePenetrationBloomFilter.contains(requestParam.getPhoneNumber());
        UserDO userDO;
        if (isExist) {
            // 如果布隆过滤器认为用户可能存在，会有误判问题，需要再从数据库查询是否真实存在该用户进行验证
            LambdaQueryWrapper<UserDO> queryWrapper = Wrappers.lambdaQuery(UserDO.class)
                    .eq(UserDO::getPhoneNumber, requestParam.getPhoneNumber())
                    .eq(UserDO::getDelFlag, 0);
            userDO = userMapper.selectOne(queryWrapper);
            // 如果布隆过滤器误判（用户实际不存在），处理逻辑
            if (userDO == null) {
                // 布隆过滤器误判时，说明用户实际不存在，需要执行新增逻辑，并将手机号添加到布隆过滤器中
                userDO = registerNewUser(requestParam);
                userRegisterCachePenetrationBloomFilter.add(requestParam.getPhoneNumber());
            }
        } else {
            // 布隆过滤器判断用户不存在，直接执行新增逻辑，并将手机号添加到布隆过滤器中
            userDO = registerNewUser(requestParam);
            userRegisterCachePenetrationBloomFilter.add(requestParam.getPhoneNumber());
        }
        // 避免重复生成 token的开销
        String tokenCacheKey = RedisCacheConstant.USER_LOGIN_KEY + requestParam.getPhoneNumber();
        String token = stringRedisTemplate.opsForValue().get(tokenCacheKey);
        if (!StrUtil.isEmpty(token)) {
            return UserLoginRespDTO.builder()
                    .token(token)
                    .build();
        }
        // 生成 JWT token 并返回
        Map<String, Object> playLoad = new HashMap<>();
        playLoad.put(JWTPlayLoadConstant.LOGIN_PLAY_LOAD_ID, userDO.getId());
        playLoad.put(JWTPlayLoadConstant.LOGIN_PLAY_LOAD_USERNAME, userDO.getPhoneNumber());
        token = JWTUtils.generateToken(playLoad, tokenExpire);
        stringRedisTemplate.opsForValue().set(tokenCacheKey, token, tokenExpire, TimeUnit.SECONDS);
        return UserLoginRespDTO.builder()
                .token(token)
                .build();
    }

    /**
     * 注册新用户
     *
     * @param requestParam 用户登录请求参数
     * @return 新注册的用户信息
     */
    private UserDO registerNewUser(UserLoginReqDTO requestParam) {
        UserDO userDO = UserDO.builder()
                .sex(UserSexEnums.UNKNOWN.getType())
                .email("未知")
                // 使用默认头像
                .headShot(defaultHeadShot)
                // 使用默认昵称
                .nickName(new Faker().name().username())
                .school("未知")
                .status(UserStatusEnums.NORMAL.getType())
                .wechat("未知")
                .professional("未知")
                .phoneNumber(requestParam.getPhoneNumber())
                .build();
        try {
            int insert = userMapper.insert(userDO);
            if (insert < 1) {
                throw new ClientException("新增用户失败！");
            }
        } catch (DuplicateKeyException ex) {
            // 用户记录已存在，触发唯一索引冲突
            throw new ClientException("该用户信息已存在！");
        }
        return userDO;
    }
}
